﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using pk3DS.Core;
using pk3DS.Core.CTR;

namespace pk3DS.WinForms;

public sealed partial class OWSE : Form
{
    public OWSE()
    {
        InitializeComponent();

        // Script Drag&Drop
        AllowDrop = true;
        DragEnter += TabMain_DragEnter;
        DragDrop += TabMain_DragDrop;

        // Finished
        OpenQuick(Directory.GetFiles("encdata"));
        mapView.Show();
        tb_Zone.SelectedIndex = 1;  // Show Overworlds tab
    }

    private readonly string[] gameLocations = Main.Config.GetText(TextName.metlist_000000);
    private string[] filepaths;
    private string[] encdatapaths;
    private byte[] masterZoneData;
    private bool debugToolDumping;

    // Generated Storage
    private string[] zdLocations;
    private string[] rawLocations;
    private byte[][] locationData;

    // Map Viewer References
    internal static Zone CurrentZone;
    internal static MapMatrix mm;
    private readonly MapPermView mapView = new();

    private void OpenQuick(string[] encdata)
    {
        // Gather
        encdatapaths = encdata;
        Array.Sort(encdatapaths);
        filepaths = encdatapaths.Skip(Main.Config.ORAS ? 2 : 1).Take(encdatapaths.Length - (Main.Config.ORAS ? 2 : 1)).ToArray();
        masterZoneData = File.ReadAllBytes(encdatapaths[0]);
        zdLocations = new string[filepaths.Length];
        rawLocations = new string[filepaths.Length];

        tb_File5.Visible = Main.Config.ORAS; // 5th File is only present with OR/AS.

        // Analyze
        for (int f = 0; f < filepaths.Length; f++)
        {
            string name = Path.GetFileNameWithoutExtension(filepaths[f]);

            int LocationNum = Convert.ToInt16(name[4..]);
            var zo = new ZoneData(masterZoneData.Skip(f * ZoneData.Size).Take(ZoneData.Size).ToArray());
            string LocationName = gameLocations[zo.ParentMap];
            zdLocations[f] = LocationNum.ToString("000") + " - " + LocationName;
            rawLocations[f] = LocationName;
        }

        // Assign
        CB_LocationID.DataSource = zdLocations;
        CB_LocationID.Enabled = true;
        CB_LocationID_SelectedIndexChanged(null, null);
        NUD_WMap.Maximum = zdLocations.Length; // Cap map warp destinations to the amount of maps.
    }

    private void B_Map_Click(object sender, EventArgs e)
    {
        if (!mapView.Visible)
            mapView.Show();
    }

    private void ClosingForm(object sender, FormClosingEventArgs e)
    {
        // Close map view
        mapView.Close();
        mapView.Dispose();
    }

    private void CB_LocationID_SelectedIndexChanged(object sender, EventArgs e)
    {
        SetEntry();
        entry = CB_LocationID.SelectedIndex;
        GetEntry();
    }

    private void GetEntry()
    {
        if (entry < 0) return;
        byte[] raw = File.ReadAllBytes(filepaths[entry]);
        locationData = Mini.UnpackMini(raw, "ZO");
        if (locationData == null) return;

        // Read master ZD table
        byte[] zd = masterZoneData.Skip(ZoneData.Size * entry).Take(ZoneData.Size).ToArray();
        RTB_ZDMaster.Lines = Scripts.GetHexLines(zd, 0x10);

        // Load from location Data.
        CurrentZone = new Zone(locationData);
        // File 0 - ZoneData
        RTB_ZD.Lines = Scripts.GetHexLines(locationData[0], 0x10);
        GetZoneData();

        // File 1 - Overworld Setup & Script
        RTB_OWSC.Lines = Scripts.GetHexLines(locationData[1], 0x10);
        GetOWSData();

        // File 2 - Map Script
        RTB_MapSC.Lines = Scripts.GetHexLines(locationData[2], 0x10);
        GetScriptData();

        // File 3 - Encounters
        RTB_Encounter.Lines = Scripts.GetHexLines(locationData[3], 0x10);

        // File 4 - ?? (ORAS Only?)
        RTB_File5.Lines = Scripts.GetHexLines(locationData.Length <= 4 ? null : locationData[4], 0x10);
    }

    private void SetEntry()
    {
        if (entry < 0) return;
        if (debugToolDumping) return;

        // Set the data back into the class object
        // Currently only the first two files.
        SetZoneData(); // File 0
        SetOWSData();
        // setMSData();
        // setEncounterData();
        // if (Main.Config.ORAS)
        //     setUnknown();

        // Reassemble files
        byte[][] data = CurrentZone.Write();

        // Debug Check (can stay, why not.)
        if (!locationData.Where((t, i) => !data[i].SequenceEqual(t)).Any())
            return;

        // Util.Alert("Zone has been edited!");
        System.Media.SystemSounds.Asterisk.Play();

        // Package the files into the permanent package file.
        byte[] raw = Mini.PackMini(data, "ZO");
        File.WriteAllBytes(filepaths[entry], raw);
    }

    // Loading of Data
    private void GetZoneData()
    {
        L_ZDPreview.Text = "Text File: " + CurrentZone.ZD.TextFile
                                         + Environment.NewLine + "Map File: " + CurrentZone.ZD.MapMatrix;

        L_ZD.Text = string.Format("X: {0,5}{3}Y: {1,5}{3}Z:{2,6}{3}{3}X: {4,5}{3}Y: {5,5}{3}Z:{6,6}", CurrentZone.ZD.PX, CurrentZone.ZD.PY,
            CurrentZone.ZD.Z, Environment.NewLine, CurrentZone.ZD.PX2, CurrentZone.ZD.PY2,
            CurrentZone.ZD.Z2);

        if (Math.Abs(CurrentZone.ZD.PX - CurrentZone.ZD.PX2) > 0.01
            || Math.Abs(CurrentZone.ZD.PY - CurrentZone.ZD.PY2) > 0.01
            || CurrentZone.ZD.Z != CurrentZone.ZD.Z2)
        {
            L_ZD.Text += Environment.NewLine + "COORDINATE MISMATCH";
        }

        // Fetch Map Image
        mapView.SetMap(CurrentZone.ZD.MapMatrix);
    }

    private void GetOWSData()
    {
        // Reset Fields a little.
        RTB_F.Text = RTB_N.Text = RTB_W.Text = RTB_T1.Text = RTB_T2.Text = string.Empty;
        fEntry = nEntry = wEntry = tEntry = uEntry = -1;
        // Set Counters
        NUD_FurnCount.Value = CurrentZone.Entities.FurnitureCount; ChangeFurnitureCount(null, null);
        NUD_NPCCount.Value = CurrentZone.Entities.NPCCount; ChangeNPCCount(null, null);
        NUD_WarpCount.Value = CurrentZone.Entities.WarpCount; ChangeWarpCount(null, null);
        NUD_TrigCount.Value = CurrentZone.Entities.TriggerCount; ChangeTriggerCount(null, null);
        NUD_UnkCount.Value = CurrentZone.Entities.UnknownCount; ChangeUnkCount(null, null);

        // Collect/Load Data
        NUD_FE.Value = NUD_FE.Maximum < 0 ? -1 : 0; ChangeFurniture(null, null);
        NUD_NE.Value = NUD_NE.Maximum < 0 ? -1 : 0; ChangeNPC(null, null);
        NUD_WE.Value = NUD_WE.Maximum < 0 ? -1 : 0; ChangeWarp(null, null);
        NUD_TE.Value = NUD_TE.Maximum < 0 ? -1 : 0; ChangeTrigger1(null, null);
        NUD_UE.Value = NUD_UE.Maximum < 0 ? -1 : 0; ChangeTrigger2(null, null);

        // Process Scripts
        var script = CurrentZone.Entities.Script;
        if (script.Raw.Length > 4)
        {
            RTB_OS.Lines = Scripts.GetHexLines(script.Raw);
            L_OWSCDesc.Text = script.Info;

            uint[] Instructions = script.DecompressedInstructions;
            RTB_OWSCMD.Lines = Scripts.GetHexLines(Instructions);

            if (script.DecompressedLength / 4 != Instructions.Length)
                RTB_OWSCMD.Text = RTB_OSP.Text = "DCMP FAIL";
            else
                RTB_OSP.Lines = [.. script.ParseScript, .. script.ParseMoves];
        }
        else
        {
            RTB_OWSCMD.Lines = RTB_OS.Lines = ["No Data"];
        }
    }

    private void GetScriptData()
    {
        var script = CurrentZone.MapScript.Script;
        if (script.Raw.Length > 4)
        {
            RTB_MS.Lines = Scripts.GetHexLines(script.Raw);
            L_MSSCDesc.Text = script.Info;

            uint[] Instructions = script.DecompressedInstructions;
            RTB_MSCMD.Lines = Scripts.GetHexLines(Instructions);

            if (script.DecompressedLength / 4 != Instructions.Length)
                RTB_MSCMD.Text = RTB_OSP.Text = "DCMP FAIL";
            else
                RTB_MSP.Lines = [.. script.ParseScript, .. script.ParseMoves];
        }
        else
        {
            RTB_MSCMD.Lines = RTB_OS.Lines = ["No Data"];
        }
    }

    private static void SetZoneData()
    {
        // Nothing, ZoneData is not currently researched enough.
    }

    private void SetOWSData()
    {
        // Force all entities to be written back
        SetFurniture();
        SetNPC();
        SetWarp();
        SetTrigger1();
        SetTrigger2();
    }

    // Overworld Viewing
    private int entry = -1;
    private int fEntry, nEntry, wEntry, tEntry, uEntry = -1;
    #region Enabling
    internal static void ToggleEnable(NumericUpDown master, NumericUpDown slave, GroupBox display)
    {
        slave.Maximum = master.Value - 1;
        slave.Enabled = display.Visible = slave.Maximum > -1;
        slave.Minimum = slave.Enabled ? 0 : -1;
    }

    private void ChangeFurnitureCount(object sender, EventArgs e)
    {
        // Resize array
        int count = (int)NUD_FurnCount.Value;
        CurrentZone.Entities.FurnitureCount = count;
        Array.Resize(ref CurrentZone.Entities.Furniture, count);
        for (int i = 0; i < count; i++)
            CurrentZone.Entities.Furniture[i] ??= new Zone.ZoneEntities.EntityFurniture();

        ToggleEnable(NUD_FurnCount, NUD_FE, GB_F);
    }

    private void ChangeNPCCount(object sender, EventArgs e)
    {
        // Resize array
        int count = (int)NUD_NPCCount.Value;
        CurrentZone.Entities.NPCCount = count;
        Array.Resize(ref CurrentZone.Entities.NPCs, count);
        for (int i = 0; i < count; i++)
            CurrentZone.Entities.NPCs[i] ??= new Zone.ZoneEntities.EntityNPC();

        ToggleEnable(NUD_NPCCount, NUD_NE, GB_N);
    }

    private void ChangeWarpCount(object sender, EventArgs e)
    {
        // Resize array
        int count = (int)NUD_WarpCount.Value;
        CurrentZone.Entities.WarpCount = count;
        Array.Resize(ref CurrentZone.Entities.Warps, count);
        for (int i = 0; i < count; i++)
            CurrentZone.Entities.Warps[i] ??= new Zone.ZoneEntities.EntityWarp();

        ToggleEnable(NUD_WarpCount, NUD_WE, GB_W);
    }

    private void ChangeTriggerCount(object sender, EventArgs e)
    {
        // Resize array
        int count = (int)NUD_TrigCount.Value;
        CurrentZone.Entities.TriggerCount = count;
        Array.Resize(ref CurrentZone.Entities.Triggers1, count);
        for (int i = 0; i < count; i++)
            CurrentZone.Entities.Triggers1[i] ??= new Zone.ZoneEntities.EntityTrigger1();

        ToggleEnable(NUD_TrigCount, NUD_TE, GB_T1);
    }

    private void ChangeUnkCount(object sender, EventArgs e)
    {
        // Resize array
        int count = (int)NUD_UnkCount.Value;
        CurrentZone.Entities.UnknownCount = count;
        Array.Resize(ref CurrentZone.Entities.Triggers2, count);
        for (int i = 0; i < count; i++)
            CurrentZone.Entities.Triggers2[i] ??= new Zone.ZoneEntities.EntityTrigger2();

        ToggleEnable(NUD_UnkCount, NUD_UE, GB_T2);
    }
    #endregion
    #region Updating
    private void ChangeFurniture(object sender, EventArgs e)
    {
        if (NUD_FE.Value < 0) return;
        SetFurniture();
        fEntry = (int)NUD_FE.Value;
        GetFurniture();
    }

    private void GetFurniture()
    {
        if (NUD_FE.Value < 0) return;

        var Furniture = CurrentZone.Entities.Furniture[fEntry];
        NUD_FX.Value = Furniture.X;
        NUD_FY.Value = Furniture.Y;
        NUD_FWX.Value = Furniture.WX;
        NUD_FWY.Value = Furniture.WY;
        RTB_F.Text = Util.GetHexString(Furniture.Raw);
    }

    private void SetFurniture()
    {
        if (NUD_FE.Value < 0) return;
        if (fEntry < 0) return;

        var FUrniture = CurrentZone.Entities.Furniture[fEntry];
        FUrniture.X = (int)NUD_FX.Value;
        FUrniture.Y = (int)NUD_FY.Value;
        FUrniture.WX = (int)NUD_FWX.Value;
        FUrniture.WY = (int)NUD_FWY.Value;
    }

    private void ChangeNPC(object sender, EventArgs e)
    {
        if (NUD_NE.Value < 0) return;
        SetNPC();
        nEntry = (int)NUD_NE.Value;
        GetNPC();
    }

    private void GetNPC()
    {
        if (NUD_NE.Value < 0) return;
        var NPC = CurrentZone.Entities.NPCs[nEntry];

        // Load new Attributes
        NUD_NID.Value = NPC.ID;
        NUD_NModel.Value = NPC.Model;
        NUD_NFlag.Value = NPC.SpawnFlag;
        NUD_NScript.Value = NPC.Script;
        NUD_NFace.Value = NPC.FaceDirection;
        NUD_NRange.Value = NPC.SightRange;
        NUD_NX.Value = NPC.X;
        NUD_NY.Value = NPC.Y;
        NUD_NMove1.Value = NPC.MovePermissions;
        NUD_NMove2.Value = NPC.MovePermissions2;

        // Uneditables
        TB_NDeg.Text = NPC.Deg18.ToString(CultureInfo.InvariantCulture);
        TB_Leash.Text = NPC.L1 == NPC.L2 && NPC.L2 == NPC.L3 && NPC.L3 == -1
            ? TB_Leash.Text = "No Leash!"
            : $"{NPC.L1}, {NPC.L2}, {NPC.L3} -- {NPC.LDir}";

        RTB_N.Text = Util.GetHexString(NPC.Raw);
    }

    private void SetNPC()
    {
        if (NUD_NE.Value < 0) return;
        if (nEntry < 0) return;

        var NPC = CurrentZone.Entities.NPCs[nEntry];
        NPC.ID = (int)NUD_NID.Value;
        NPC.Model = (int)NUD_NModel.Value;
        NPC.SpawnFlag = (int)NUD_NFlag.Value;
        NPC.Script = (int)NUD_NScript.Value;
        NPC.FaceDirection = (int)NUD_NFace.Value;
        NPC.SightRange = (int)NUD_NRange.Value;
        NPC.X = (int)NUD_NX.Value;
        NPC.Y = (int)NUD_NY.Value;

        NPC.MovePermissions = (int)NUD_NMove1.Value;
        NPC.MovePermissions2 = (int)NUD_NMove2.Value;
    }

    private void ChangeWarp(object sender, EventArgs e)
    {
        if (NUD_WE.Value < 0) return;
        SetWarp();
        wEntry = (int)NUD_WE.Value;
        GetWarp();
    }

    private void GetWarp()
    {
        if (NUD_WE.Value < 0) return;

        var Warp = CurrentZone.Entities.Warps[wEntry];
        RTB_W.Text = Util.GetHexString(Warp.Raw);

        // Load new Attributes
        NUD_WMap.Value = Warp.DestinationMap;
        NUD_WTile.Value = Warp.DestinationTileIndex;

        NUD_WX.Value = Warp.X;
        NUD_WY.Value = Warp.Y;

        // Flavor Mods
        L_WarpDest.Text = zdLocations[Warp.DestinationMap];
    }

    private void SetWarp()
    {
        if (NUD_WE.Value < 0) return;
        if (wEntry < 0) return;

        var Warp = CurrentZone.Entities.Warps[wEntry];
        Warp.DestinationMap = (int)NUD_WMap.Value;
        Warp.DestinationTileIndex = (int)NUD_WTile.Value;
        Warp.X = (short)NUD_WX.Value;
        Warp.Y = (short)NUD_WY.Value;
    }

    private void ChangeTrigger1(object sender, EventArgs e)
    {
        if (NUD_TE.Value < 0) return;
        SetTrigger1();
        tEntry = (int)NUD_TE.Value;
        GetTrigger1();
    }

    private void GetTrigger1()
    {
        if (NUD_TE.Value < 0) return;

        var Trigger1 = CurrentZone.Entities.Triggers1[tEntry];
        NUD_T1X.Value = Trigger1.X;
        NUD_T1Y.Value = Trigger1.Y;
        RTB_T1.Text = Util.GetHexString(Trigger1.Raw);
    }

    private void SetTrigger1()
    {
        if (NUD_TE.Value < 0) return;
        if (tEntry < 0) return;

        var Trigger1 = CurrentZone.Entities.Triggers1[tEntry];
        Trigger1.X = (int)NUD_T1X.Value;
        Trigger1.Y = (int)NUD_T1Y.Value;
    }

    private void ChangeTrigger2(object sender, EventArgs e)
    {
        if (NUD_UE.Value < 0) return;
        SetTrigger2();
        uEntry = (int)NUD_UE.Value;
        GetTrigger2();
    }

    private void GetTrigger2()
    {
        if (NUD_UE.Value < 0) return;

        // Load New Data
        var Trigger2 = CurrentZone.Entities.Triggers2[uEntry];
        NUD_T2X.Value = Trigger2.X;
        NUD_T2Y.Value = Trigger2.Y;
        RTB_T2.Text = Util.GetHexString(Trigger2.Raw);
    }

    private void SetTrigger2()
    {
        if (NUD_UE.Value < 0) return;
        if (uEntry < 0) return;

        var Trigger2 = CurrentZone.Entities.Triggers2[uEntry];
        Trigger2.X = (int)NUD_T2X.Value;
        Trigger2.Y = (int)NUD_T2Y.Value;
    }
    #endregion

    // Overworld User Enhancements
    private void ChangeNPC_ID(object sender, EventArgs e) => L_NID.ForeColor = NUD_NID.Value != NUD_NE.Value ? Color.Red : Color.Black;
    private void ChangeNPC_Model(object sender, EventArgs e) => L_ModelAsHex.Text = "0x" + ((int)NUD_NModel.Value).ToString("X4");

    private void DclickDestMap(object sender, EventArgs e)
    {
        var Tile = NUD_WTile.Value;
        CB_LocationID.SelectedIndex = (int)NUD_WMap.Value;
        try
        { NUD_WE.Value = Tile; }
        catch
        { try { NUD_WE.Value = 0; } catch { } }
    }

    private void ChangeWarp_X(object sender, EventArgs e) => L_WpX.Text = (NUD_WX.Value / 18).ToString(CultureInfo.InvariantCulture);
    private void ChangeWarp_Y(object sender, EventArgs e) => L_WpY.Text = (NUD_WY.Value / 18).ToString(CultureInfo.InvariantCulture);

    // Script Handling
    private void B_HLCMD_Click(object sender, EventArgs e)
    {
        int ctr = WinFormsUtil.HighlightText(RTB_OSP, "**", Color.Red) + (WinFormsUtil.HighlightText(RTB_MSP, "**", Color.Red) / 2);
        WinFormsUtil.Alert($"{ctr} instance{(ctr > 1 ? "s" : "")} of \"*\" present.");
    }

    private void TabMain_DragEnter(object sender, DragEventArgs e)
    {
        if (e.Data.GetDataPresent(DataFormats.FileDrop)) e.Effect = DragDropEffects.Copy;
    }

    private void TabMain_DragDrop(object sender, DragEventArgs e)
    {
        string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
        string path = files[0]; // open first D&D
        if (new FileInfo(path).Length < 10000000)
            ParseScriptInput(File.ReadAllBytes(path));
    }

    private void ParseScriptInput(byte[] data)
    {
        var scr = new Script(data);
        RTB_CompressedScript.Lines = Scripts.GetHexLines(scr.CompressedBytes);
        System.Media.SystemSounds.Asterisk.Play();
    }

    private void PasteScript(object sender, EventArgs e)
    {
        // import data as bytes
        try
        {
            string text = RTB_CompressedScript.Text.Replace(Environment.NewLine, "").Replace("\n", "").Replace(" ", "");
            byte[] data = Util.StringToByteArray(text);

            byte[] dec = Scripts.DecompressScript(data);

            RTB_DecompressedScript.Lines = Scripts.GetHexLines(dec);
        }
        catch
        {
            RTB_DecompressedScript.Text = "DECMP ERROR";
        }
    }

    // Dev Dumpers
    private void B_DumpFurniture_Click(object sender, EventArgs e)
    {
        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Export all Furniture?") != DialogResult.Yes)
            return;

        debugToolDumping = true;
        List<string> result = [];
        List<byte[]> data = [];
        for (int i = 0; i < CB_LocationID.Items.Count; i++)
        {
            CB_LocationID.SelectedIndex = i;
            for (int j = 0; j < CurrentZone.Entities.FurnitureCount; j++)
            {
                result.Add(Util.GetHexString(CurrentZone.Entities.Furniture[j].Raw));
                data.Add(CurrentZone.Entities.Furniture[j].Raw);
            }
        }
        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Write Furniture to file?") == DialogResult.Yes)
            File.WriteAllBytes("Furniture.bin", data.SelectMany(z => z).ToArray());

        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Copy Furniture to Clipboard?") == DialogResult.Yes)
            Clipboard.SetText(string.Join(Environment.NewLine, result));

        CB_LocationID.SelectedIndex = 0;
        debugToolDumping = false;
    }

    private void B_DumpNPC_Click(object sender, EventArgs e)
    {
        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Export all NPCs?") != DialogResult.Yes)
            return;

        debugToolDumping = true;
        List<string> result = [];
        List<byte[]> data = [];
        for (int i = 0; i < CB_LocationID.Items.Count; i++)
        {
            CB_LocationID.SelectedIndex = i;
            for (int j = 0; j < CurrentZone.Entities.NPCCount; j++)
            {
                result.Add(Util.GetHexString(CurrentZone.Entities.NPCs[j].Raw));
                data.Add(CurrentZone.Entities.NPCs[j].Raw);
            }
        }
        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Write NPCs to file?") == DialogResult.Yes)
            File.WriteAllBytes("NPCs.bin", data.SelectMany(z => z).ToArray());

        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Copy NPCs to Clipboard?") == DialogResult.Yes)
            Clipboard.SetText(string.Join(Environment.NewLine, result));

        CB_LocationID.SelectedIndex = 0;
        debugToolDumping = false;
    }

    private void B_DumpWarp_Click(object sender, EventArgs e)
    {
        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Export all Warps?") != DialogResult.Yes)
            return;

        debugToolDumping = true;
        List<string> result = [];
        List<byte[]> data = [];
        for (int i = 0; i < CB_LocationID.Items.Count; i++)
        {
            CB_LocationID.SelectedIndex = i;
            for (int j = 0; j < CurrentZone.Entities.WarpCount; j++)
            {
                result.Add(Util.GetHexString(CurrentZone.Entities.Warps[j].Raw));
                data.Add(CurrentZone.Entities.Warps[j].Raw);
            }
        }
        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Write Warps to file?") == DialogResult.Yes)
            File.WriteAllBytes("Warps.bin", data.SelectMany(z => z).ToArray());

        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Copy Warps to Clipboard?") == DialogResult.Yes)
            Clipboard.SetText(string.Join(Environment.NewLine, result));

        CB_LocationID.SelectedIndex = 0;
        debugToolDumping = false;
    }

    private void B_DumpTrigger_Click(object sender, EventArgs e)
    {
        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Export all Triggers?") != DialogResult.Yes)
            return;

        debugToolDumping = true;
        List<string> result = [];
        List<byte[]> data = [];
        for (int i = 0; i < CB_LocationID.Items.Count; i++)
        {
            CB_LocationID.SelectedIndex = i;
            for (int j = 0; j < CurrentZone.Entities.TriggerCount; j++)
            {
                result.Add(Util.GetHexString(CurrentZone.Entities.Triggers1[j].Raw));
                data.Add(CurrentZone.Entities.Triggers1[j].Raw);
            }
        }
        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Write Triggers to file?") == DialogResult.Yes)
            File.WriteAllBytes("Triggers.bin", data.SelectMany(z => z).ToArray());

        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Copy Triggers to Clipboard?") == DialogResult.Yes)
            Clipboard.SetText(string.Join(Environment.NewLine, result));

        CB_LocationID.SelectedIndex = 0;
        debugToolDumping = false;
    }

    private void B_DumpUnk_Click(object sender, EventArgs e)
    {
        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Export all Unks?") != DialogResult.Yes)
            return;

        debugToolDumping = true;
        List<string> result = [];
        List<byte[]> data = [];
        for (int i = 0; i < CB_LocationID.Items.Count; i++)
        {
            CB_LocationID.SelectedIndex = i;
            for (int j = 0; j < CurrentZone.Entities.UnknownCount; j++)
            {
                result.Add(Util.GetHexString(CurrentZone.Entities.Triggers2[j].Raw));
                data.Add(CurrentZone.Entities.Triggers2[j].Raw);
            }
        }
        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Write Unks to file?") == DialogResult.Yes)
            File.WriteAllBytes("Unks.bin", data.SelectMany(z => z).ToArray());

        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Copy Unks to Clipboard?") == DialogResult.Yes)
            Clipboard.SetText(string.Join(Environment.NewLine, result));

        CB_LocationID.SelectedIndex = 0;
        debugToolDumping = false;
    }

    private void B_DumpMaps_Click(object sender, EventArgs e)
    {
        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Export all MapImages?") != DialogResult.Yes)
            return;

        debugToolDumping = true;
        const string folder = "MapImages";
        if (!Directory.Exists(folder))
            Directory.CreateDirectory(folder);

        string[] result = new string[CB_LocationID.Items.Count];
        for (int i = 0; i < CB_LocationID.Items.Count; i++)
        {
            mapView.DrawMap = i;
            Image img = mapView.GetMapImage(crop: true);
            using (var ms = new MemoryStream())
            {
                //error will throw from here
                img.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                byte[] data = ms.ToArray();
                File.WriteAllBytes(Path.Combine(folder, $"{zdLocations[i].Replace('?', '-')} ({i}).png"), data);
            }
            string l = mm.EntryList.Where(t => t != 0xFFFF).Aggregate("", (current, t) => current + t.ToString("000 "));
            result[i] = $"{i:000}\t{CB_LocationID.Items[i]}\t{l}";
        }
        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Write Map parse output?") == DialogResult.Yes)
            File.WriteAllLines("MapLocations.txt", result);
        CB_LocationID.SelectedIndex = 0;
        WinFormsUtil.Alert("All Map images have been dumped to " + folder + ".");
        debugToolDumping = false;
    }

    private void B_DumpZD_Click(object sender, EventArgs e)
    {
        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Export all ZD?") != DialogResult.Yes)
            return;

        debugToolDumping = true;
        List<string> result = [];
        List<byte[]> data = [];
        for (int i = 0; i < CB_LocationID.Items.Count; i++)
        {
            CB_LocationID.SelectedIndex = i;
            result.Add(Util.GetHexString(CurrentZone.ZD.Data));
            data.Add(CurrentZone.ZD.Data);
        }
        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Write ZDs to file?") == DialogResult.Yes)
            File.WriteAllBytes("ZDs.bin", data.SelectMany(z => z).ToArray());

        if (WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Copy ZDs to Clipboard?") == DialogResult.Yes)
            Clipboard.SetText(string.Join(Environment.NewLine, result));

        CB_LocationID.SelectedIndex = 0;
        debugToolDumping = false;
    }

    // Raw file editing
    private void ChangeRAWCheck(object sender, EventArgs e)
    {
        bool chk = ((CheckBox)sender).Checked;
        foreach (NumericUpDown nud in GB_F.Controls.OfType<NumericUpDown>())
            nud.Enabled = !chk;
        foreach (NumericUpDown nud in GB_N.Controls.OfType<NumericUpDown>())
            nud.Enabled = !chk;
        foreach (NumericUpDown nud in GB_W.Controls.OfType<NumericUpDown>())
            nud.Enabled = !chk;
        foreach (NumericUpDown nud in GB_T1.Controls.OfType<NumericUpDown>())
            nud.Enabled = !chk;
        foreach (NumericUpDown nud in GB_T2.Controls.OfType<NumericUpDown>())
            nud.Enabled = !chk;

        foreach (RichTextBox rtb in new[] { RTB_F, RTB_N, RTB_W, RTB_T1, RTB_T2 })
            rtb.Visible = chk;
    }

    private void ChangeRAW_F(object sender, EventArgs e)
    {
        if (sender is not RichTextBox { Visible: true })
            return;

        try
        {
            byte[] data = Util.StringToByteArray(((RichTextBox)sender).Text.Replace(Environment.NewLine, " ").Replace(" ", ""));
            if (data.Length != Zone.ZoneEntities.EntityFurniture.Size)
                return;
            CurrentZone.Entities.Furniture[fEntry].Raw = data;
            GetFurniture();
        }
        catch
        {
            ((RichTextBox)sender).Text = Util.GetHexString(CurrentZone.Entities.Furniture[fEntry].Raw);
        }
    }

    private void ChangeRAW_N(object sender, EventArgs e)
    {
        if (sender is not RichTextBox { Visible: true })
            return;

        try
        {
            byte[] data = Util.StringToByteArray(((RichTextBox)sender).Text.Replace(Environment.NewLine, " ").Replace(" ", ""));
            if (data.Length != Zone.ZoneEntities.EntityNPC.Size)
                return;
            CurrentZone.Entities.NPCs[nEntry].Raw = data;
            GetNPC();
        }
        catch
        {
            ((RichTextBox)sender).Text = Util.GetHexString(CurrentZone.Entities.NPCs[nEntry].Raw);
        }
    }

    private void ChangeRAW_W(object sender, EventArgs e)
    {
        if (sender is not RichTextBox { Visible: true })
            return;

        try
        {
            byte[] data = Util.StringToByteArray(((RichTextBox)sender).Text.Replace(Environment.NewLine, " ").Replace(" ", ""));
            if (data.Length != Zone.ZoneEntities.EntityWarp.Size)
                return;
            CurrentZone.Entities.Warps[wEntry].Raw = data;
            GetWarp();
        }
        catch
        {
            ((RichTextBox)sender).Text = Util.GetHexString(CurrentZone.Entities.Warps[wEntry].Raw);
        }
    }

    private void ChangeRAW_T1(object sender, EventArgs e)
    {
        if (sender is not RichTextBox { Visible: true })
            return;

        try
        {
            byte[] data = Util.StringToByteArray(((RichTextBox)sender).Text.Replace(Environment.NewLine, " ").Replace(" ", ""));
            if (data.Length != Zone.ZoneEntities.EntityTrigger1.Size)
                return;
            CurrentZone.Entities.Triggers1[tEntry].Raw = data;
            GetTrigger1();
        }
        catch
        {
            ((RichTextBox)sender).Text = Util.GetHexString(CurrentZone.Entities.Triggers1[tEntry].Raw);
        }
    }

    private void ChangeRAW_T2(object sender, EventArgs e)
    {
        if (sender is not RichTextBox { Visible: true })
            return;

        try
        {
            byte[] data = Util.StringToByteArray(((RichTextBox)sender).Text.Replace(Environment.NewLine, " ").Replace(" ", ""));
            if (data.Length != Zone.ZoneEntities.EntityTrigger2.Size)
                return;
            CurrentZone.Entities.Triggers2[uEntry].Raw = data;
            GetTrigger2();
        }
        catch
        {
            ((RichTextBox)sender).Text = Util.GetHexString(CurrentZone.Entities.Triggers2[uEntry].Raw);
        }
    }

    // RAW Resets
    private void B_ResetOverworlds_Click(object sender, EventArgs e)
    {
        if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, "Reset all overworld entities?"))
            return;

        // since scripts are not editable, just reset the overworld file.
        CurrentZone.Entities = new Zone.ZoneEntities(locationData[1]);
        GetOWSData();
    }

    private void B_ResetFurniture_Click(object sender, EventArgs e)
    {
        CurrentZone.Entities.Furniture[fEntry].OriginalData.CopyTo(CurrentZone.Entities.Furniture[fEntry].Raw, 0);
        GetFurniture();
    }

    private void B_ResetNPC_Click(object sender, EventArgs e)
    {
        CurrentZone.Entities.NPCs[nEntry].OriginalData.CopyTo(CurrentZone.Entities.NPCs[nEntry].Raw, 0);
        GetNPC();
    }

    private void B_ResetWarp_Click(object sender, EventArgs e)
    {
        CurrentZone.Entities.Warps[wEntry].OriginalData.CopyTo(CurrentZone.Entities.Warps[wEntry].Raw, 0);
        GetWarp();
    }

    private void B_ResetTrigger1_Click(object sender, EventArgs e)
    {
        CurrentZone.Entities.Triggers1[tEntry].OriginalData.CopyTo(CurrentZone.Entities.Triggers1[tEntry].Raw, 0);
        GetTrigger1();
    }

    private void B_ResetTrigger2_Click(object sender, EventArgs e)
    {
        CurrentZone.Entities.Triggers2[uEntry].OriginalData.CopyTo(CurrentZone.Entities.Triggers2[uEntry].Raw, 0);
        GetTrigger2();
    }
}